package au.com.handsunited.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import org.apache.commons.lang.StringUtils;
import org.hibernate.search.annotations.Boost;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import au.com.handsunited.model.language.Language;
import au.com.handsunited.model.message.MessageBoard;
import au.com.handsunited.model.notices.NoticeBoard;
import au.com.handsunited.model.relationship.Relationship;
import au.com.handsunited.model.relationship.RelationshipStatus;
import au.com.handsunited.model.relationship.Role;
import au.com.handsunited.model.servicecategory.ServiceCategory;
import au.com.handsunited.model.skill.Skill;

/**
 *
 * @author arunda
 */
@Indexed
public abstract class BaseEntity extends MotherOfAllEntities implements Comparable<BaseEntity> {

	private static final String DEFAULT_IMG = "default.jpg";

	@Field(index=Index.TOKENIZED, store=Store.NO)
	@Boost(4f)
	private String name;
	private String imgSrc;
	@Field(index=Index.TOKENIZED, store=Store.NO)
	private String description;
	@Field(index=Index.TOKENIZED, store=Store.NO)
	@Boost(2.5f)
	private String summary;

	private ContactInfo contactInfo;
	@IndexedEmbedded
	private Address location;
	private Availability availability;

	private Set<Language> languages;
	private Set<Skill> skills;
	private Set<ServiceCategory> serviceCategories;

	private Set<Relationship> groupRelationships;
	private Set<Relationship> organisationRelationships;
	private Set<Relationship> projectRelationships;
	private Set<Relationship> userRelationships;

	private BaseEntity owner;
	private MessageBoard messageBoard;
	private NoticeBoard noticeBoard;

	private Boolean receiveEmailNotifications;
	private Boolean workFromHome;
	private Boolean workOnSite;



	private static Logger logger = LoggerFactory.getLogger(BaseEntity.class);


	public BaseEntity() {
		super();
		contactInfo = new ContactInfo();
		skills = new TreeSet<Skill>();
		serviceCategories = new TreeSet<ServiceCategory>();
		languages = new TreeSet<Language>();
		location = new Address();
		messageBoard = new MessageBoard();
		noticeBoard = new NoticeBoard();
		availability = new Availability();
		imgSrc = BaseEntity.DEFAULT_IMG;

		groupRelationships = new HashSet<Relationship>();
		organisationRelationships = new HashSet<Relationship>();
		projectRelationships = new HashSet<Relationship>();
		userRelationships = new HashSet<Relationship>();
	}



	public Relationship findRelationshipWith (BaseEntity entity) {

		if (entity.isGroup()) {
			return findRelationshipWith(entity, getGroupRelationships());
		} else if (entity.isOrganisation()) {
			return findRelationshipWith(entity, getOrganisationRelationships());
		} else if (entity.isProject()) {
			return findRelationshipWith(entity, getProjectRelationships());
		} else if (entity.isUser()) {
			return findRelationshipWith(entity, getUserRelationships());
		}
		return null;
	}


	public Relationship createRelationshipWith(BaseEntity entity, Role role, RelationshipStatus status) {

		if (entity.isGroup()) {
			return createRelationshipWithGroup((Group)entity, role, status);
		} else if (entity.isOrganisation()) {
			return createRelationshipWithOrganisation((Organisation)entity, role, status);
		} else if (entity.isProject()) {
			return createRelationshipWithProject((Project)entity, role, status);
		} else if (entity.isUser()) {
			return createRelationshipWithUser((User)entity, role, status);
		}
		return null;
	}


	public Relationship removeRelationshipWith(BaseEntity entity) {
		if (entity.isGroup()) {
			return removeRelationshipWithGroup((Group) entity);
		} else if (entity.isOrganisation()){
			return removeRelationshipWithOrganisation((Organisation) entity);
		} else if (entity.isProject()) {
			return removeRelationshipWithProject((Project) entity);
		} else if (entity.isUser()) {
			return removeRelationshipWithUser((User) entity);
		}
		return null;
	}


	public Relationship createRelationshipWithUser(User user, Role role, RelationshipStatus status) {
		return createRelationshipWith(user, role, status, userRelationships);
	}
	public Relationship removeRelationshipWithUser(User user) {
		return removeRelationshipWith(user, userRelationships);
	}

	public Relationship createRelationshipWithGroup (Group group, Role role, RelationshipStatus status) {
		return createRelationshipWith(group, role, status, groupRelationships);
	}
	public Relationship removeRelationshipWithGroup(Group group) {
		return removeRelationshipWith(group, groupRelationships);
	}

	public Relationship createRelationshipWithOrganisation (Organisation organisation, Role role, RelationshipStatus status) {
		return createRelationshipWith(organisation, role, status, organisationRelationships);
	}
	public Relationship removeRelationshipWithOrganisation(Organisation organisation) {
		return removeRelationshipWith(organisation, organisationRelationships);
	}

	public Relationship createRelationshipWithProject(Project project, Role role, RelationshipStatus status) {
		return createRelationshipWith(project, role, status, projectRelationships);
	}
	public Relationship removeRelationshipWithProject(Project project) {
		return removeRelationshipWith(project, projectRelationships);
	}


	public List<Relationship> getActiveUserRelationships() {
		return getRelationshipsFromListWithStatus(userRelationships, RelationshipStatus.ACTIVE);
	}
	public List<Relationship> getInactiveUserRelationships() {
		return getRelationshipsFromListWithStatus(userRelationships, RelationshipStatus.INACTIVE);
	}
	public List<Relationship> getActionPendingUserRelationships() {
		return getRelationshipsFromListWithStatus(userRelationships, RelationshipStatus.ACTION_PENDING);
	}
	public List<Relationship> getAwaitingAuthorisationUserRelationships() {
		return getRelationshipsFromListWithStatus(userRelationships, RelationshipStatus.AWAITING_AUTHORISATION);
	}



	public int getNoOfActiveUserRelationships() {
		return getActiveUserRelationships().size();
	}
	public int getNoOfInactiveUserRelationships() {
		return getInactiveUserRelationships().size();
	}
	public int getNoOfActionPendingUserRelationships() {
		return getActionPendingUserRelationships().size();
	}
	public int getNoOfAwaitingAuthorisationUserRelationships() {
		return getAwaitingAuthorisationUserRelationships().size();
	}


	public List<Relationship> getActiveProjectRelationships() {
		return getRelationshipsFromListWithStatus(projectRelationships, RelationshipStatus.ACTIVE);
	}
	public List<Relationship> getInactiveProjectRelationships() {
		return getRelationshipsFromListWithStatus(projectRelationships, RelationshipStatus.INACTIVE);
	}
	public List<Relationship> getActionPendingProjectRelationships() {
		return getRelationshipsFromListWithStatus(projectRelationships, RelationshipStatus.ACTION_PENDING);
	}
	public List<Relationship> getAwaitingAuthorisationProjectRelationships() {
		return getRelationshipsFromListWithStatus(projectRelationships, RelationshipStatus.AWAITING_AUTHORISATION);
	}


	public int getNoOfActiveProjectRelationships() {
		return getActiveProjectRelationships().size();
	}
	public int getNoOfInactiveProjectRelationships() {
		return getInactiveProjectRelationships().size();
	}
	public int getNoOfActionPendingProjectRelationships() {
		return getActionPendingProjectRelationships().size();
	}
	public int getNoOfAwaitingAuthorisationProjectRelationships() {
		return getAwaitingAuthorisationProjectRelationships().size();
	}




	public List<Relationship> getActiveGroupRelationships() {
		return getRelationshipsFromListWithStatus(groupRelationships, RelationshipStatus.ACTIVE);
	}
	public List<Relationship> getInactiveGroupRelationships() {
		return getRelationshipsFromListWithStatus(groupRelationships, RelationshipStatus.INACTIVE);
	}
	public List<Relationship> getActionPendingGroupRelationships() {
		return getRelationshipsFromListWithStatus(groupRelationships, RelationshipStatus.ACTION_PENDING);
	}
	public List<Relationship> getAwaitingAuthorisationGroupRelationships() {
		return getRelationshipsFromListWithStatus(groupRelationships, RelationshipStatus.AWAITING_AUTHORISATION);
	}


	public int getNoOfActiveGroupRelationships() {
		return getActiveGroupRelationships().size();
	}
	public int getNoOfInactiveGroupRelationships() {
		return getInactiveGroupRelationships().size();
	}
	public int getNoOfActionPendingGroupRelationships() {
		return getActionPendingGroupRelationships().size();
	}
	public int getNoOfAwaitingAuthorisationGroupRelationships() {
		return getAwaitingAuthorisationGroupRelationships().size();
	}


	public List<Relationship> getActiveOrganisationRelationships() {
		return getRelationshipsFromListWithStatus(organisationRelationships, RelationshipStatus.ACTIVE);
	}
	public List<Relationship> getInactiveOrganisationRelationships() {
		return getRelationshipsFromListWithStatus(organisationRelationships, RelationshipStatus.INACTIVE);
	}
	public List<Relationship> getActionPendingOrganisationRelationships() {
		return getRelationshipsFromListWithStatus(organisationRelationships, RelationshipStatus.ACTION_PENDING);
	}
	public List<Relationship> getAwaitingAuthorisationOrganisationRelationships() {
		return getRelationshipsFromListWithStatus(organisationRelationships, RelationshipStatus.AWAITING_AUTHORISATION);
	}


	public int getNoOfActiveOrganisationRelationships() {
		return getActiveOrganisationRelationships().size();
	}
	public int getNoOfInactiveOrganisationRelationships() {
		return getInactiveOrganisationRelationships().size();
	}
	public int getNoOfActionPendingOrganisationRelationships() {
		return getActionPendingOrganisationRelationships().size();
	}
	public int getNoOfAwaitingAuthorisationOrganisationRelationships() {
		return getAwaitingAuthorisationOrganisationRelationships().size();
	}



	public List<Relationship> getActiveRelationships() {
		return getRelationshipsWithStatus(RelationshipStatus.ACTIVE);
	}
	public List<Relationship> getInactiveRelationships() {
		return getRelationshipsWithStatus(RelationshipStatus.INACTIVE);
	}
	public List<Relationship> getActionPendingRelationships() {
		return getRelationshipsWithStatus(RelationshipStatus.ACTIVE);
	}
	public List<Relationship> getAwaitingAuthorisationRelationships() {
		return getRelationshipsWithStatus(RelationshipStatus.AWAITING_AUTHORISATION);
	}


	public int getNoOfActiveRelationships() {
		return getActiveRelationships().size();
	}
	public int getNoOfInactiveRelationships() {
		return getInactiveRelationships().size();
	}
	public int getNoOfActionPendingRelationships() {
		return getActionPendingRelationships().size();
	}
	public int getNoOfAwaitingAuthorisationRelationships() {
		return getAwaitingAuthorisationRelationships().size();
	}



	private List<Relationship> getRelationshipsWithStatus(RelationshipStatus status) {

		List<Relationship> relationships = new ArrayList<Relationship>();
		relationships.addAll(getRelationshipsFromListWithStatus(userRelationships, status));
		relationships.addAll(getRelationshipsFromListWithStatus(groupRelationships, status));
		relationships.addAll(getRelationshipsFromListWithStatus(organisationRelationships, status));
		relationships.addAll(getRelationshipsFromListWithStatus(projectRelationships, status));
		return relationships;
	}

	private List<Relationship> getRelationshipsFromListWithStatus (Collection<Relationship> relationshipCollection, RelationshipStatus status) {
		List<Relationship> relationships = new ArrayList<Relationship>();
		for (Relationship relationship : relationshipCollection) {
			if (relationship.getStatus() == status) {
				relationships.add(relationship);
			}
		}
		return relationships;
	}


	public List<Group> getGroupsInNetwork() {

		List<Group> groups = new ArrayList<Group>();

		for (Relationship relationship : getActiveGroupRelationships()) {
			groups.add((Group)relationship.getRelative());
		}
		return groups;
	}


	public List<Organisation> getOrganisationsInNetwork() {
		List<Organisation> organisations = new ArrayList<Organisation>();

		for (Relationship relationship : getActiveOrganisationRelationships()) {
			organisations.add((Organisation)relationship.getRelative());
		}
		return organisations;
	}


	public List<Project> getProjectsInNetwork() {

		List<Project> projects = new ArrayList<Project>();

		for (Relationship relationship : getActiveProjectRelationships()) {
			projects.add((Project)relationship.getRelative());
		}
		return projects;
	}


	public List<User> getUsersInNetwork() {
		List<User> users = new ArrayList<User>();

		for (Relationship relationship : getActiveUserRelationships()) {
			users.add((User)relationship.getRelative());
		}
		return users;
	}




	public Map<Long, Group> getMapOfGroupsInNetwork() {

		Map<Long, Group> groupsMap = new TreeMap<Long, Group>();

		for (Group group : getGroupsInNetwork()) {
			groupsMap.put(group.getId(), group);
		}
		return groupsMap;
	}

	public Map<Long, Organisation> getMapOfOrganisationsInNetwork() {

		Map<Long, Organisation> organisationsMap = new TreeMap<Long, Organisation>();
		for (Organisation organisation : getOrganisationsInNetwork()) {
			organisationsMap.put(organisation.getId(), organisation);
		}
		return organisationsMap;
	}

	public Map<Long, Project> getMapOfProjectsInNetwork() {

		Map<Long, Project> projectsMap = new TreeMap<Long, Project>();
		for (Project project : getProjectsInNetwork()) {
			projectsMap.put(project.getId(), project);
		}
		return projectsMap;
	}

	public Map<Long, User> getMapOfUsersInNetwork() {

		Map<Long, User> usersMap = new TreeMap<Long, User>();
		for (User user : getUsersInNetwork()) {
			usersMap.put(user.getId(), user);
		}
		return usersMap;
	}



	public List<Organisation> getOwnedOrganisations() {

		List<Organisation> ownedOrganisations = new ArrayList<Organisation>();
		for (Relationship relation : organisationRelationships) {
			if (relation.getRelative().getOwner().getId() == getId()) {
				ownedOrganisations.add((Organisation)relation.getRelative());
			}
		}
		return ownedOrganisations;
	}


	public List<Project> getOwnedProjects() {

		List<Project> ownedProjects = new ArrayList<Project>();
		for (Relationship relation : projectRelationships) {
			if (relation.getRelative().getOwner().getId() == getId()) {
				ownedProjects.add((Project)relation.getRelative());
			}
		}
		return ownedProjects;
	}



	public String getEntityType() {
		return "entity";
	}


	public boolean isOwnerOf(BaseEntity entity) {
		if (entity != null) {
			if (getId() != null && getId() == entity.getId()) {
				return true;
			} else {
				return entity.getOwner().getId() == getId();
			}
		} else {
			return false;
		}
	}

	public boolean isAdministratorOf(BaseEntity entity) {

		if (getId() != null && getId().equals(entity.getId())) {
			return true;
		} else {
			Relationship relationship = entity.findRelationshipWith(this);

			if (relationship != null && (relationship.getRole() == Role.ADMINISTRATOR || relationship.getRole() == Role.OWNER)) {
				return true;
			}
		}

		return false;
	}


//	public boolean canBeUpdatedBy(BaseEntity possibleUpdater) {
//		if (getId().equals(possibleUpdater.getId())) {
//			return true;
//		} else if (owner.getId().equals(possibleUpdater.getId())) {
//			return true;
//		} else if (possibleUpdater.isAdministratorOf(this)) {
//			return true;
//		} else {
//			return false;
//		}
//	}


	private Relationship createRelationshipWith(BaseEntity e, Role role, RelationshipStatus status, Set<Relationship> existingRelationships) {

		Relationship relationship = findRelationshipWith(e, existingRelationships);

		if (relationship == null) {

			relationship = new Relationship(this, e, role, status);
			existingRelationships.add(relationship);

			if (BaseEntity.logger.isDebugEnabled()) {
				BaseEntity.logger.debug("{}[{}] is now in a Relationship of status[{}] with {}[{}].",
						new String[] {getEntityType(), getName(), relationship.getStatus().getName() , e.getEntityType(), e.getName()});
			}

			return relationship;

		} else {
			if (BaseEntity.logger.isDebugEnabled()) {
				BaseEntity.logger.debug("{}[{}] is already in a relationship with {}[{}]",
						new String[] {getEntityType(), getName(), e.getEntityType(), e.getName()});
			}
			return null;
		}
	}


	private Relationship removeRelationshipWith(BaseEntity entity, Set<Relationship> existingRelationships) {

		for (Relationship relationship : existingRelationships) {
			if (relationship.getRelative().getId() == entity.getId()) {
				existingRelationships.remove(relationship);
				return relationship;
			}
		}
		return null;
	}

	private Relationship findRelationshipWith (BaseEntity e, Set<Relationship> relationships) {

		for (Relationship relationship : relationships) {
			if (e.getId().equals(relationship.getRelative().getId()) && relationship.getStatus() != RelationshipStatus.INACTIVE) {
				return relationship;
			}
		}

		if (BaseEntity.logger.isDebugEnabled()) {
			BaseEntity.logger.debug("{}[{}] does not have a relationship with {}[{}].",
					new String[] {getEntityType(), getName(), e.getEntityType(), e.getName()});
		}

		return null;
	}


	public String getImgSrc() {
		return imgSrc;
	}


	public void setImgSrc(String imgSrc) {
		if (StringUtils.isNotBlank(imgSrc)) {
			this.imgSrc = imgSrc;
		} else {
			this.imgSrc = BaseEntity.DEFAULT_IMG;
		}
	}


	public Address getLocation() {
		if (location == null) {
			location = new Address();
		}
		return location;
	}


	public void setLocation(Address location) {
		this.location = location;
	}

	public ContactInfo getContactInfo() {
		return contactInfo;
	}

	public void setContactInfo(ContactInfo contactInfo) {
		this.contactInfo = contactInfo;
	}


	public void setSkills(Set<Skill> skills) {
		this.skills = skills;
	}

	public Set<Skill> getSkills() {
		return skills;
	}

	public Set<ServiceCategory> getServiceCategories() {
		return serviceCategories;
	}

	public void setServiceCategories(Set<ServiceCategory> serviceCategories) {
		this.serviceCategories = serviceCategories;
	}

	public Availability getAvailability() {
		if (availability == null) {
			availability = new Availability();
		}
		return availability;
	}

	public void setAvailability(Availability availability) {
		this.availability = availability;
	}

	public Set<Language> getLanguages() {
		return languages;
	}

	public void setLanguages(Set<Language> languages) {
			this.languages = languages;
	}

	public boolean isReceiveEmailNotifications() {
		return receiveEmailNotifications;
	}

	public void setReceiveEmailNotifications(boolean receiveEmailNotifications) {
		this.receiveEmailNotifications = receiveEmailNotifications;
	}

	public boolean isWorkFromHome() {
		return workFromHome;
	}

	public boolean isWorkOnSite() {
		return workOnSite;
	}

	public void setWorkFromHome(boolean workFromHome) {
		this.workFromHome = workFromHome;
	}

	public void setWorkOnSite(boolean workOnSite) {
		this.workOnSite = workOnSite;
	}

	public int getGroupNetworkSize() {
		return getNoOfActiveGroupRelationships();
	}

	public int getOrganisationNetworkSize() {
		return getNoOfActiveOrganisationRelationships();
	}

	public int getProjectNetworkSize() {
		return getNoOfActiveProjectRelationships();
	}

	public int getUserNetworkSize() {
		return getNoOfActiveUserRelationships();
	}

	public boolean getHasGroupsInNetwork() {
		return !groupRelationships.isEmpty();
	}

	public boolean getHasOrganisationsInNetwork() {
		return !organisationRelationships.isEmpty();
	}

	public boolean getHasProjectsInNetwork() {
		return !projectRelationships.isEmpty();
	}

	public boolean getHasUsersInNetwork() {
		return !userRelationships.isEmpty();
	}

	public boolean isGroup() {
		return false;
	}

	public boolean isOrganisation() {
		return false;
	}

	public boolean isProject() {
		return false;
	}

	public boolean isUser() {
		return false;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getSummary() {
		return summary;
	}

	public void setSummary(String summary) {
		this.summary = summary;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public BaseEntity getOwner() {
		return owner;
	}


	public void setOwner(BaseEntity owner) {
		this.owner = owner;
	}

	public MessageBoard getMessageBoard() {
		return messageBoard;
	}

	public void setMessageBoard(MessageBoard messageBoard) {
		this.messageBoard = messageBoard;
	}

	public NoticeBoard getNoticeBoard() {
		return noticeBoard;
	}

	public void setNoticeBoard(NoticeBoard noticeBoard) {
		this.noticeBoard = noticeBoard;
	}

	public Set<Relationship> getGroupRelationships() {
		return groupRelationships;
	}

	public void setGroupRelationships(Set<Relationship> groupRelationships) {
		this.groupRelationships = groupRelationships;
	}

	public Set<Relationship> getOrganisationRelationships() {
		return organisationRelationships;
	}

	public void setOrganisationRelationships(Set<Relationship> organisationRelationships) {
		this.organisationRelationships = organisationRelationships;
	}

	public Set<Relationship> getProjectRelationships() {
		return projectRelationships;
	}

	public void setProjectRelationships(Set<Relationship> projectRelationships) {
		this.projectRelationships = projectRelationships;
	}

	public Set<Relationship> getUserRelationships() {
		return userRelationships;
	}

	public void setUserRelationships(Set<Relationship> userRelationships) {
		this.userRelationships = userRelationships;
	}

	/**
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		return super.equals(obj);
	}

	/**
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return super.hashCode();
	}


	/**
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(BaseEntity e) {
		return getName().compareToIgnoreCase(e.getName());
	}

}
